package com.cn.ycy.filter;

import com.cn.ycy.filter.entity.CachedBodyOutputMessage;
import com.cn.ycy.filter.entity.DefaultServerRequest;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;

@Component
public class SelfModifyRequestResponseFilterGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {

  @Override
  public GatewayFilter apply(NameValueConfig config) {
    return new ModifyRequestResponseGatewayFilter();
  }

  class ModifyRequestResponseGatewayFilter implements GatewayFilter, Ordered {

    private final Logger logger = LoggerFactory.getLogger(SelfModifyRequestBodyGatewayFilterFactory.RequestHandlerFilter.class);

    private final List<HttpMessageReader<?>> messageReaders;

    public ModifyRequestResponseGatewayFilter() {
      this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
    }

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

      logger.info(exchange.getRequest().getMethodValue());

      String echostr = exchange.getRequest().getQueryParams().getFirst("echostr");

      if (StringUtils.isNotBlank(echostr)) {
        //使用msg_signature验签 首次登陆验签
        return firstCheck(exchange, echostr);
      }

      ServerRequest serverRequest = new DefaultServerRequest(exchange, this.messageReaders);

      Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(requestBody -> {
        logger.info("requestBody {}", requestBody);
        //模拟抛出异常
//        if (requestBody != null) throw new RuntimeException("wdl");
//        if (requestBody != null) return Mono.error(new RuntimeException("wdl"));
        //解密
        return Mono.just("<xml><ToUserName><![CDATA[wxf5c6ef6ab8b3d357]]></ToUserName>\n" +
                "<Content><![CDATA[一点毛病没有]]></Content>\n" +
                "</xml>");
      });

      BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
      HttpHeaders headers = new HttpHeaders();
      headers.putAll(exchange.getRequest().getHeaders());

      // the new content type will be computed by bodyInserter
      // and then set in the request decorator
      headers.remove(HttpHeaders.CONTENT_LENGTH);

      CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);

      return bodyInserter.insert(outputMessage, new BodyInserterContext())
              // .log("modify_request", Level.INFO)
              .then(Mono.defer(() -> {
                ServerHttpRequest decorator = decorate(exchange, headers, outputMessage);
                //改写请求和响应
                return chain.filter(exchange.mutate().request(decorator).response(decorate(exchange)).build());
              }));

//      return chain.filter(exchange.mutate().response(decorate(exchange)).build());

    }

    /**
     * 直接响应客户端 message
     */
    public Mono<Void> firstCheck(ServerWebExchange exchange, String message) {
      return Mono.defer(() -> {
        byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
        ServerHttpResponse response = exchange.getResponse();
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        return response.writeWith(Flux.just(buffer));
      });
    }

    private ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
      return new ServerHttpRequestDecorator(exchange.getRequest()) {
        @Override
        public HttpHeaders getHeaders() {
          long contentLength = headers.getContentLength();
          HttpHeaders httpHeaders = new HttpHeaders();
          httpHeaders.putAll(super.getHeaders());
          if (contentLength > 0) {
            httpHeaders.setContentLength(contentLength);
          } else {
            // TODO: this causes a 'HTTP/1.1 411 Length Required' // on
            // httpbin.org
            httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
          }
          return httpHeaders;
        }

        @Override
        public Flux<DataBuffer> getBody() {
          return outputMessage.getBody();
        }
      };
    }

    @SuppressWarnings("unchecked")
    private ServerHttpResponseDecorator decorate(ServerWebExchange exchange) {
      return new ServerHttpResponseDecorator(exchange.getResponse()) {

        @Override
        public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

//          Class inClass = config.getInClass();
//          Class outClass = config.getOutClass();

          String originalResponseContentType = exchange
                  .getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
          HttpHeaders httpHeaders = new HttpHeaders();
          // explicitly add it in this way instead of
          // 'httpHeaders.setContentType(originalResponseContentType)'
          // this will prevent exception in case of using non-standard media
          // types like "Content-Type: image"
          httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);

          ClientResponse clientResponse = ClientResponse
                  .create(exchange.getResponse().getStatusCode())
                  .headers(headers -> headers.putAll(httpHeaders))
                  .body(Flux.from(body)).build();

          // TODO: flux or mono
          Mono modifiedBody = clientResponse.bodyToMono(String.class)
                  .flatMap(originalBody -> {
                    logger.info("response {}", originalBody);
                    //加密
                    return Mono.just("我是网关，我改写了响应");
                  });

          BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
                  String.class);
          CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
                  exchange, exchange.getResponse().getHeaders());
          return bodyInserter.insert(outputMessage, new BodyInserterContext())
                  .then(Mono.defer(() -> {
                    Flux<DataBuffer> messageBody = outputMessage.getBody();
                    HttpHeaders headers = getDelegate().getHeaders();
                    if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
                      messageBody = messageBody.doOnNext(data -> headers
                              .setContentLength(data.readableByteCount()));
                    }
                    // TODO: fail if isStreamingMediaType?
                    return getDelegate().writeWith(messageBody);
                  }));
        }

        @Override
        public Mono<Void> writeAndFlushWith(
                Publisher<? extends Publisher<? extends DataBuffer>> body) {
          return writeWith(Flux.from(body).flatMapSequential(p -> p));
        }
      };
    }

    @Override
    public int getOrder() {
      return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
    }

  }

}
